﻿/*******************************************************************************
Copyright (c) 2011-2013, PKStudio
Copyright (c) 2011,      Zynga Inc.
Copyright (c) 2008-2010, Ricardo Quesada

http://pkstudio.org

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*******************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;

namespace PKEngine
{
	/// <summary>
	/// An interval action is an action that takes place within a certain period of time.
	/// It has an start time, and a finish time. The finish time is the parameter duration plus the start time.
	/// </summary>
	public class PKActionInterval : PKFiniteTimeAction
	{
		#region Private Fields
		private bool isFirstTick;
		#endregion

		#region Public Properties
		/// <summary>
		/// Elapsed.
		/// </summary>
		public float Elapsed { get; private set; }

		/// <summary>
		/// Return true if the action has finished.
		/// </summary>
		public override bool IsDone
		{
			get
			{
				return this.Elapsed >= this.Duration;
			}
		}
		#endregion

		#region Initialization
		public PKActionInterval(float duration)
		{
			if (duration == 0)
			{
				this.Duration = float.Epsilon;
			}
			else
			{
				this.Duration = duration;
			}

			this.isFirstTick = true;
			this.Elapsed = 0;
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);

			isFirstTick = true;
			Elapsed = 0;
		}

		public override void Step(float dt)
		{
			if (isFirstTick)
			{
				Elapsed = 0;
				isFirstTick = false;
			}
			else
			{
				Elapsed += dt;
			}
			this.Update(Math.Max(0, Math.Min(1.0f, Elapsed / Math.Max(float.Epsilon, Duration))));
		}
		#endregion
	}

	/// <summary>
	/// Runs actions sequentially, one after another.
	/// </summary>
	public class PKSequence : PKActionInterval
	{
		#region Private Fields
		private PKFiniteTimeAction action1;
		private PKFiniteTimeAction action2;
		private float split;
		private int last;
		#endregion

		#region Initialization
		public PKSequence(PKFiniteTimeAction one, PKFiniteTimeAction two)
			: base(0)
		{
			Debug.Assert(one != null && two != null, "PKSequence: Argument cannot be null.");

			float d = one.Duration + two.Duration;
			if (d != 0)
			{
				this.Duration = d;
			}

			this.action1 = one;
			this.action2 = two;
		}

		public PKSequence(PKFiniteTimeAction[] actions)
			: base(0)
		{
			this.action1 = actions[0];
			for (int i = 1; i < actions.Length - 1; i++)
			{
				this.action1 = new PKSequence(this.action1, actions[i]);
			}
			this.action2 = actions[actions.Length - 1];

			float d = this.action1.Duration + this.action2.Duration;
			if (d != 0)
			{
				this.Duration = d;
			}
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);

			split = action1.Duration / Math.Max(this.Duration, float.Epsilon);
			last = -1;
		}

		public override void Stop()
		{
			switch (last)
			{
				case 0:
					action1.Stop();
					break;
				case 1:
					action2.Stop();
					break;
			}

			base.Stop();
		}

		public override void Update(float time)
		{
			int found = 0;
			float newTime = 0;

			if (time < split)
			{
				// action1
				found = 0;
				if (split != 0)
				{
					newTime = time / split;
				}
				else
				{
					newTime = 1;
				}
			}
			else
			{
				// acrion2
				found = 1;
				if (split == 1)
				{
					newTime = 1;
				}
				else
				{
					newTime = (time - split) / (1 - split);
				}
			}

			if (found == 1)
			{
				if (last == -1)
				{
					// action1 was skipped, execute it.
					action1.StartWithTarget(Target);
					action1.Update(1.0f);
					action1.Stop();
				}
				else if (last == 0)
				{
					// switching to action2. stop action1.
					action1.Update(1.0f);
					action1.Stop();
				}
			}
			else if (found == 0 && last == 1)
			{
				action2.Update(0);
				action2.Stop();
			}

			if (found == 0)
			{
				if (found == last && action1.IsDone)
				{
					// Last action found and it is done.
					return;
				}
				if (found != last)
				{
					// New action. Start it.
					action1.StartWithTarget(Target);
				}
				action1.Update(newTime);
			}
			else if (found == 1)
			{
				if (found == last && action2.IsDone)
				{
					// Last action found and it is done.
					return;
				}
				if (found != last)
				{
					// New action. Start it.
					action2.StartWithTarget(Target);
				}
				action2.Update(newTime);
			}

			last = found;
		}

		public override PKFiniteTimeAction Reverse()
		{
			return new PKSequence(action2.Reverse(), action1.Reverse());
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKSequence(action1.Copy(), action2.Copy());
		}
		#endregion
	}

	/// <summary>
	/// Repeats an action a number of times.
	/// </summary>
	public class PKRepeat : PKActionInterval
	{
		#region Private Fields
		private int times;
		private int total;
		private float nextDt;
		private bool isActionInstant;
		#endregion

		#region Public Properties
		/// <summary>
		/// Inner action.
		/// </summary>
		public PKFiniteTimeAction InnerAction { get; private set; }

		/// <summary>
		/// Return true if the action has finished.
		/// </summary>
		public override bool IsDone
		{
			get
			{
				return this.total == this.times;
			}
		}
		#endregion

		#region Initialization
		public PKRepeat(PKFiniteTimeAction action, int times)
			: base(action.Duration * times)
		{
			Debug.Assert(action != null, "PKRepeat: Argument cannot be null.");

			this.times = times;
			this.total = 0;
			this.InnerAction = action;
			this.isActionInstant = (action is PKActionInstant) ? true : false;

			//a instant action needs to be executed one time less in the update method since it uses startWithTarget to execute the action
			if (this.isActionInstant)
			{
				this.times--;
			}
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			total = 0;
			nextDt = InnerAction.Duration / Duration;
			base.StartWithTarget(target);
			InnerAction.StartWithTarget(target);
		}

		public override void Stop()
		{
			InnerAction.Stop();
			base.Stop();
		}

		public override void Update(float time)
		{
			if (time > nextDt)
			{
				while (time > nextDt && total < times)
				{
					InnerAction.Update(1.0f);
					total++;

					InnerAction.Stop();
					InnerAction.StartWithTarget(Target);
					nextDt += InnerAction.Duration / Duration;
				}

				if (time > 1.0f && total < times)
				{
					total++;
				}

				if (!isActionInstant)
				{
					if (total == times)
					{
						InnerAction.Update(1.0f);
						InnerAction.Stop();
					}
					else
					{
						InnerAction.Update(time - (nextDt - InnerAction.Duration / Duration));
					}
				}
			}
			else
			{
				float currentTime = time * times;
				currentTime = currentTime - (float)Math.Floor(currentTime);
				InnerAction.Update(currentTime);
			}
		}

		public override PKFiniteTimeAction Reverse()
		{
			return new PKRepeat(InnerAction.Reverse() as PKActionInterval, times);
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKRepeat(InnerAction.Copy(), times);
		}
		#endregion
	}

	/// <summary>
	/// Spawn a new action immediately.
	/// </summary>
	public class PKSpwan : PKActionInterval
	{
		#region Private Fields
		private PKFiniteTimeAction action1;
		private PKFiniteTimeAction action2;
		#endregion

		#region Initialization
		public PKSpwan(PKFiniteTimeAction one, PKFiniteTimeAction two)
			: base(0)
		{
			Debug.Assert(one != null && two != null, "PKSpwan: Argument cannot be null.");

			float d1 = one.Duration;
			float d2 = two.Duration;
			float d = Math.Max(d1, d2);
			if (d != 0)
			{
				this.Duration = d;
			}

			this.action1 = one;
			this.action2 = two;

			if (d1 > d2)
			{
				this.action2 = new PKSequence(two, new PKDelayTime(d1 - d2));
			}
			else if (d1 < d2)
			{
				this.action1 = new PKSequence(one, new PKDelayTime(d2 - d1));
			}
		}

		public PKSpwan(PKFiniteTimeAction[] actions)
			: base(0)
		{
			action1 = actions[0];
			for (int i = 1; i < actions.Length - 1; i++)
			{
				action1 = new PKSpwan(action1, actions[i]);
			}
			action2 = actions[actions.Length - 1];

			float d1 = action1.Duration;
			float d2 = action2.Duration;

			float d = Math.Max(d1, d2);
			if (d != 0)
			{
				this.Duration = d;
			}

			if (d1 > d2)
			{
				action2 = new PKSequence(action2, new PKDelayTime(d1 - d2));
			}
			else if (d1 < d2)
			{
				action1 = new PKSequence(action1, new PKDelayTime(d2 - d1));
			}
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			action1.StartWithTarget(target);
			action2.StartWithTarget(target);
		}

		public override void Stop()
		{
			action1.Stop();
			action2.Stop();
			base.Stop();
		}

		public override void Update(float time)
		{
			action1.Update(time);
			action2.Update(time);
		}

		public override PKFiniteTimeAction Reverse()
		{
			return new PKSpwan(action1.Reverse(), action2.Reverse());
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKSpwan(action1.Copy(), action2.Copy());
		}
		#endregion
	}

	/// <summary>
	/// Moves a CCNode object to the position.
	/// </summary>
	public class PKMoveTo : PKActionInterval
	{
		#region Private Fields
		private Point endPosition;
		private Point startPosition;
		private Point delta;
		#endregion

		#region Initialization
		public PKMoveTo(float duration, Point position)
			: base(duration)
		{
			this.endPosition = position;
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			startPosition = target.Position;
			delta = new Point(endPosition.X - startPosition.X, endPosition.Y - startPosition.Y);
		}

		public override void Update(float time)
		{
			Target.Position = new Point(startPosition.X + (int)(delta.X * time), startPosition.Y + (int)(delta.Y * time));
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKMoveTo(Duration, endPosition);
		}
		#endregion
	}

	/// <summary>
	/// Moves a CCNode object by relative distance.
	/// </summary>
	public class PKMoveBy : PKActionInterval
	{
		#region Private Fields
		private Point startPosition;
		private Point delta;
		#endregion

		#region Initialization
		public PKMoveBy(float duration, Point distance)
			: base(duration)
		{
			this.delta = distance;
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			startPosition = target.Position;
		}

		public override void Update(float time)
		{
			Target.Position = new Point(startPosition.X + (int)(delta.X * time), startPosition.Y + (int)(delta.Y * time));
		}

		public override PKFiniteTimeAction Reverse()
		{
			return new PKMoveBy(Duration, new Point(-delta.X, -delta.Y));
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKMoveBy(Duration, new Point(delta.X, delta.Y));
		}
		#endregion
	}

	/// <summary>
	/// Moves a CCNode object to a parabolic position simulating a jump movement.
	/// </summary>
	public class PKJumpTo : PKActionInterval
	{
		#region Private Fields
		private Point endPosition;
		private Point startPosition;
		private Point delta;
		private float height;
		private int jumps;
		#endregion

		#region Initialization
		public PKJumpTo(float duration, Point position, float height, int jumps)
			: base(duration)
		{
			this.endPosition = position;
			this.height = height;
			this.jumps = jumps;
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			startPosition = target.Position;
			delta = new Point(endPosition.X - startPosition.X, endPosition.Y - startPosition.Y);
		}

		public override void Update(float time)
		{
			// Parabolic jump.
			float frac = time * jumps;
			frac = frac - (float)Math.Floor(frac);
			float y = height * 4 * frac * (1 - frac);
			y += delta.Y * time;
			float x = delta.X * time;
			Target.Position = new Point(startPosition.X + (int)x, startPosition.Y + (int)y);
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKJumpTo(Duration, endPosition, height, jumps);
		}
		#endregion
	}

	/// <summary>
	/// Moves a CCNode object simulating a parabolic jump movement.
	/// </summary>
	public class PKJumpBy : PKActionInterval
	{
		#region Private Fields
		private Point startPosition;
		private Point delta;
		private float height;
		private int jumps;
		#endregion

		#region Initialization
		public PKJumpBy(float duration, Point position, float height, int jumps)
			: base(duration)
		{
			this.delta = position;
			this.height = height;
			this.jumps = jumps;
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			startPosition = target.Position;
		}

		public override void Update(float time)
		{
			// Parabolic jump.
			float frac = time * jumps;
			frac = frac - (float)Math.Floor(frac);
			float y = height * 4 * frac * (1 - frac);
			y += delta.Y * time;
			float x = delta.X * time;
			Target.Position = new Point(startPosition.X + (int)x, startPosition.Y + (int)y);
		}

		public override PKFiniteTimeAction Reverse()
		{
			return new PKJumpBy(Duration, new Point(-delta.X, -delta.Y), height, jumps);
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKJumpBy(Duration, new Point(delta.X, delta.Y), height, jumps);
		}
		#endregion
	}

	/// <summary>
	/// Blinks a CCNode object by modifying it's visible attribute.
	/// </summary>
	public class PKBlink : PKActionInterval
	{
		#region Private Fields
		private int times;
		private bool originalVisible;
		#endregion

		#region Initialization
		public PKBlink(float duration, int blinks)
			: base(duration)
		{
			this.times = blinks;
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			originalVisible = target.IsVisible;
		}

		public override void Stop()
		{
			Target.IsVisible = originalVisible;
			base.Stop();
		}

		public override void Update(float time)
		{
			if (!IsDone)
			{
				float m = time * times;
				m = m - (float)Math.Floor(m);
				Target.IsVisible = m > 0.5f ? true : false;
			}
		}

		public override PKFiniteTimeAction Reverse()
		{
			return new PKBlink(Duration, times);
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKBlink(Duration, times);
		}
		#endregion
	}

	/// <summary>
	/// Fades In an object. It modifies the opacity from 0 to 256.
	/// The "reverse" of this action is FadeOut.
	/// </summary>
	public class PKFadeIn : PKActionInterval
	{
		#region Initialization
		public PKFadeIn(float duration)
			: base(duration)
		{
		}
		#endregion

		#region Public Methods
		public override void Update(float time)
		{
			if (Target is IPKRgba)
			{
				(Target as IPKRgba).Opacity = (int)(256 * time);
			}
		}

		public override PKFiniteTimeAction Reverse()
		{
			return new PKFadeOut(Duration);
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKFadeIn(Duration);
		}
		#endregion
	}

	/// <summary>
	/// Fades Out an object. It modifies the opacity from 256 to 0.
	/// The "reverse" of this action is FadeIn.
	/// </summary>
	public class PKFadeOut : PKActionInterval
	{
		#region Initialization
		public PKFadeOut(float duration)
			: base(duration)
		{
		}
		#endregion

		#region Public Methods
		public override void Update(float time)
		{
			if (Target is IPKRgba)
			{
				(Target as IPKRgba).Opacity = (int)(256 * (1 - time));
			}
		}

		public override PKFiniteTimeAction Reverse()
		{
			return new PKFadeIn(Duration);
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKFadeOut(Duration);
		}
		#endregion
	}

	/// <summary>
	/// Fades an object. It modifies the opacity from the current value to a custom one.
	/// This action doesn't support "reverse".
	/// </summary>
	public class PKFadeTo : PKActionInterval
	{
		#region Private Fields
		private int toOpacity;
		private int fromOpacity;
		private int delta;
		#endregion

		#region Initialization
		public PKFadeTo(float duration, int opacity)
			: base(duration)
		{
			this.toOpacity = opacity;
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			if (Target is IPKRgba)
			{
				fromOpacity = (Target as IPKRgba).Opacity;
				delta = toOpacity - fromOpacity;
			}
		}

		public override void Update(float time)
		{
			if (Target is IPKRgba)
			{
				(Target as IPKRgba).Opacity = fromOpacity + (int)(delta * time);
			}
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKFadeTo(Duration, toOpacity);
		}
		#endregion
	}

	/// <summary>
	/// Tints a object from current tint to a custom one.
	/// </summary>
	public class PKTintTo : PKActionInterval
	{
		#region Private Fields
		private int fromRed;
		private int fromGreen;
		private int fromBlue;
		private int toRed;
		private int toGreen;
		private int toBlue;
		private int deltaRed;
		private int deltaGreen;
		private int deltaBlue;
		#endregion

		#region Initialization
		public PKTintTo(float duration, int red, int green, int blue)
			: base(duration)
		{
			this.toRed = red;
			this.toGreen = green;
			this.toBlue = blue;
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			if (Target is IPKRgba)
			{
				ushort fromColor = (Target as IPKRgba).Color;
				fromRed = PKAlpha.SharedAlpha.RedOfColor(fromColor);
				fromGreen = PKAlpha.SharedAlpha.GreenOfColor(fromColor);
				fromBlue = PKAlpha.SharedAlpha.BlueOfColor(fromColor);
				deltaRed = toRed - fromRed;
				deltaGreen = toGreen - fromGreen;
				deltaBlue = toBlue - fromBlue;
			}
		}

		public override void Update(float time)
		{
			if (Target is IPKRgba)
			{
				int r = fromRed + (int)(deltaRed * time);
				int g = fromGreen + (int)(deltaGreen * time);
				int b = fromBlue + (int)(deltaBlue * time);
				(Target as IPKRgba).Color = PKAlpha.SharedAlpha.ColorFromRgb(r, g, b);
			}
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKTintTo(Duration, toRed, toGreen, toBlue);
		}
		#endregion
	}
	/// <summary>
	/// Fades In an object's color. It modifies the color opacity from 0 to 256.
	/// The "reverse" of this action is TintFadeOut.
	/// </summary>
	public class PKTintFadeIn : PKActionInterval
	{
		#region Initialization
		public PKTintFadeIn(float duration)
			: base(duration)
		{
		}
		#endregion

		#region Public Methods
		public override void Update(float time)
		{
			if (Target is IPKRgba)
			{
				(Target as IPKRgba).ColorOpacity = (int)(256 * time);
			}
		}

		public override PKFiniteTimeAction Reverse()
		{
			return new PKTintFadeOut(Duration);
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKTintFadeIn(Duration);
		}
		#endregion
	}

	/// <summary>
	/// Fades Out an object's color. It modifies the color opacity from 256 to 0.
	/// The "reverse" of this action is TintFadeIn.
	/// </summary>
	public class PKTintFadeOut : PKActionInterval
	{
		#region Initialization
		public PKTintFadeOut(float duration)
			: base(duration)
		{
		}
		#endregion

		#region Public Methods
		public override void Update(float time)
		{
			if (Target is IPKRgba)
			{
				(Target as IPKRgba).ColorOpacity = (int)(256 * (1 - time));
			}
		}

		public override PKFiniteTimeAction Reverse()
		{
			return new PKTintFadeIn(Duration);
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKTintFadeOut(Duration);
		}
		#endregion
	}

	/// <summary>
	/// Fades an object's color. It modifies the color opacity from the current value to a custom one.
	/// This action doesn't support "reverse".
	/// </summary>
	public class PKTintFadeTo : PKActionInterval
	{
		#region Private Fields
		private int toOpacity;
		private int fromOpacity;
		private int delta;
		#endregion

		#region Initialization
		public PKTintFadeTo(float duration, int opacity)
			: base(duration)
		{
			this.toOpacity = opacity;
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			if (Target is IPKRgba)
			{
				fromOpacity = (Target as IPKRgba).ColorOpacity;
				delta = toOpacity - fromOpacity;
			}
		}

		public override void Update(float time)
		{
			if (Target is IPKRgba)
			{
				(Target as IPKRgba).ColorOpacity = fromOpacity + (int)(delta * time);
			}
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKTintFadeTo(Duration, toOpacity);
		}
		#endregion
	}

	/// <summary>
	/// Delays the action a certain amount of seconds.
	/// </summary>
	public class PKDelayTime : PKActionInterval
	{
		#region Initialization
		public PKDelayTime(float duration)
			: base(duration)
		{
		}
		#endregion

		#region Public Methods
		public override void Update(float time)
		{
			// do nothing.
		}

		public override PKFiniteTimeAction Reverse()
		{
			return new PKDelayTime(Duration);
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKDelayTime(Duration);
		}
		#endregion
	}

	/// <summary>
	/// Executes an action in reverse order, from time=duration to time=0.
	/// </summary>
	public class PKReverseTime : PKActionInterval
	{
		#region Public Properties
		public PKFiniteTimeAction InnerAction { get; private set; }
		#endregion

		#region Initialization
		public PKReverseTime(PKFiniteTimeAction action)
			: base(action.Duration)
		{
			Debug.Assert(action != null, "PKReverseTime: Argument cannot be null.");
			this.InnerAction = action;
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			InnerAction.StartWithTarget(target);
		}

		public override void Stop()
		{
			InnerAction.Stop();
			base.Stop();
		}

		public override void Update(float time)
		{
			InnerAction.Update(1 - time);
		}

		public override PKFiniteTimeAction Reverse()
		{
			return new PKReverseTime(InnerAction.Reverse());
		}

		public override PKFiniteTimeAction Copy()
		{
			return new PKReverseTime(InnerAction.Copy());
		}
		#endregion
	}

	/// <summary>
	/// Animates a sprite given the name of an Animation.
	/// </summary>
	public class PKAnimate : PKActionInterval
	{
		#region Private Fields
		float[] splitTimes;
		private int nextFrame;
		private PKSpriteFrame origFrame;
		private int executedLoops;
		#endregion

		#region Public Properties
		/// <summary>
		/// Animation used for the animate.
		/// </summary>
		public PKAnimation Animation { get; private set; }
		#endregion

		#region Initialization
		public PKAnimate(PKAnimation animation)
			: base(animation.Duration * animation.Loops)
		{
			Debug.Assert(animation != null, "PKAnimate: Argument cannot be null.");

			float singleDuration = animation.Duration;
			this.Animation = animation;
			this.nextFrame = 0;
			this.origFrame = null;
			this.executedLoops = 0;

			this.splitTimes = new float[animation.Frames.Count];

			float accumUnitsOfTime = 0;
			float newUnitOfTimeValue = singleDuration / animation.TotalDelayUnits;

			int index = 0;
			foreach (PKAnimationFrame frame in animation.Frames)
			{
				this.splitTimes[index++] = (accumUnitsOfTime * newUnitOfTimeValue) / singleDuration;
				accumUnitsOfTime += frame.DelayUnits;
			}
		}
		#endregion

		#region Public Methods
		public override void StartWithTarget(PKNode target)
		{
			base.StartWithTarget(target);
			if (Animation.RestoreOriginalFrame)
			{
				if (Target is PKSprite)
				{
					PKSprite sprite = Target as PKSprite;
					origFrame = sprite.GetDisplayFrame();
				}
			}
			nextFrame = 0;
			executedLoops = 0;
		}

		public override void Stop()
		{
			if (Animation.RestoreOriginalFrame)
			{
				if (Target is PKSprite)
				{
					PKSprite sprite = Target as PKSprite;
					sprite.SetDisplayFrame(origFrame);
				}
			}
			base.Stop();
		}

		public override void Update(float time)
		{
			// If t==1, ignore. Animation should finish with t==1.
			if (time < 1)
			{
				time *= Animation.Loops;
				// New loop?  If so, reset frame counter.
				int loopNumber = (int)time;
				if (loopNumber > executedLoops)
				{
					nextFrame = 0;
					executedLoops++;
				}
				// new t for animations
				time -= loopNumber;
			}

			for (int i = nextFrame; i < Animation.Frames.Count; i++)
			{
				float splitTime = splitTimes[i];
				if (splitTime <= time)
				{
					PKAnimationFrame animFrame = Animation.Frames[i];
					PKSpriteFrame spriteFrame = animFrame.SpriteFrame;
					if (Target is PKSprite)
					{
						PKSprite sprite = Target as PKSprite;
						sprite.SetDisplayFrame(spriteFrame);
					}

					object notificationInfo = animFrame.NotificationInfo;
					if (notificationInfo != null)
					{
						if (Target is IPKNotification)
						{
							(Target as IPKNotification).Notification(notificationInfo);
						}
					}

					nextFrame = i + 1;
				}
				else
				{
					// Fix Issue: Could be more than one frame per tick, due to low frame rate or frame delta < 1/FPS
					break;
				}
			}
		}

		public override PKFiniteTimeAction Reverse()
		{
			List<PKAnimationFrame> newList = new List<PKAnimationFrame>(Animation.Frames.Count);
			for (int i = Animation.Frames.Count - 1; i >= 0; i--)
			{
				newList.Add(Animation.Frames[i]);
			}
			PKAnimation newAnimation = new PKAnimation(newList.ToArray(), Animation.DelayPerUnit, Animation.Loops);
			return new PKAnimate(newAnimation);
		}

		public override PKFiniteTimeAction Copy()
		{
			PKAnimation newAnimation = new PKAnimation(Animation.Frames.ToArray(), Animation.DelayPerUnit, Animation.Loops);
			return new PKAnimate(newAnimation);
		}
		#endregion
	}

	/// <summary>
	/// Transit an object. It modifies the transition value from 256 to 0.
	/// </summary>
	public class PKTransit : PKActionInterval
	{
		#region Initialization
		public PKTransit(float duration)
			: base(duration)
		{
		}
		#endregion

		#region Public Methods
		public override void Update(float time)
		{
			if (Target is PKSprite)
			{
				(Target as PKSprite).TransitionValue = (int)(256 - 256 * time);
			}
		}
		#endregion
	}

}
